package com.chen.recruit.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chen.recruit.common.api.CommonResult;
import com.chen.recruit.common.constants.MessageConstants;
import com.chen.recruit.common.constants.SysConstants;
import com.chen.recruit.common.enums.CommonStatus;
import com.chen.recruit.common.enums.MenuType;
import com.chen.recruit.common.enums.ShowStatus;
import com.chen.recruit.common.utils.SysUtils;
import com.chen.recruit.domain.Role;
import com.chen.recruit.domain.RoleMenu;
import com.chen.recruit.domain.dto.MenuDto;
import com.chen.recruit.domain.vo.MetaVo;
import com.chen.recruit.mapper.RoleMenuMapper;
import com.chen.recruit.service.MenuService;
import com.chen.recruit.service.RoleService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import com.chen.recruit.domain.Menu;
import com.chen.recruit.mapper.MenuMapper;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 菜单权限Service业务层处理
 *
 * @author cgy
 * @date 2022-08-19
 */
@Service
@RequiredArgsConstructor
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {

    private final SysUtils sysUtils;
    private final RoleService roleService;
    private final RoleMenuMapper roleMenuMapper;

    @Override
    public CommonResult addOrUpdateMenu(Menu menu) {
        boolean exists = baseMapper.exists(Wrappers.<Menu>lambdaQuery()
                .eq(Menu::getName, menu.getName())
                .ne(ObjectUtil.isNotNull(menu.getId()), Menu::getId, menu.getId()));
        if (exists) {
            return CommonResult.fail(MessageConstants.MENU_NAME_REPEAT);
        }

        Menu parentMenu = baseMapper.selectById(menu.getParentId());
        if (ObjectUtil.isNotNull(parentMenu) && parentMenu.getType() == MenuType.BUTTON) {
            return CommonResult.fail(MessageConstants.PARENT_MENU_TYPE_ERROR);
        }

        if (ObjectUtil.isNotNull(menu.getId())) {
            exists = baseMapper.exists(Wrappers.<Menu>lambdaQuery().eq(Menu::getParentId, menu.getId()));
            if (exists && menu.getType() == MenuType.BUTTON) {
                return CommonResult.fail(MessageConstants.PARENT_MENU_TYPE_ERROR);
            }
            if (menu.getStatus() == CommonStatus.DISABLE) {
                exists = roleMenuMapper.exists(Wrappers.<RoleMenu>lambdaQuery().eq(RoleMenu::getMenuId, menu.getId()));
                if (exists) {
                    return CommonResult.fail(MessageConstants.MENU_STATUS_ERROR);
                }
            }
        }

        sysUtils.clearMenuCache();

        return this.saveOrUpdate(menu) ? CommonResult.success() : CommonResult.fail();
    }

    // FIXME 删除逻辑可能存在问题
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult deleteMenuById(Integer menuId) {
        boolean exists = roleMenuMapper.exists(Wrappers.<RoleMenu>lambdaQuery().eq(RoleMenu::getMenuId, menuId));
        if (exists) {
            return CommonResult.fail(MessageConstants.DELETE_MENU_FAIL);
        }

        List<Integer> menuIds = baseMapper.selectList(Wrappers.<Menu>lambdaQuery()
                .eq(Menu::getParentId, menuId)).stream().map(Menu::getId).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(menuIds)) {
            exists = roleMenuMapper.exists(Wrappers.<RoleMenu>lambdaQuery().in(RoleMenu::getMenuId, menuIds));
            if (exists) {
                return CommonResult.fail(MessageConstants.DELETE_MENU_FAIL);
            }
        }

        menuIds.add(menuId);

        sysUtils.clearMenuCache();

        return this.removeByIds(menuIds) ? CommonResult.success() : CommonResult.fail();
    }

    @Override
    public List<MenuDto> getMenusByLoginUser() {
        Object loginId = StpUtil.getLoginId();
        List<MenuDto> menuDtos = (List<MenuDto>) sysUtils.redisTemplate.opsForValue().get(SysConstants.MENU + loginId);
        if (CollUtil.isEmpty(menuDtos)) {
            List<Menu> menus = this.listByIds(getMenuIdsByUserId(loginId));
            List<MenuDto> menuDtoList = BeanUtil.copyToList(menus, MenuDto.class);
            menuDtos = menuDtoList.stream()
                    .filter(menu -> menu.getParentId() == 0 && menu.getVisible() != ShowStatus.HIDE)
                    .peek(menu -> {
                        menu.setChildren(getChildrenMenus(menu.getId(), false, menuDtoList));
                        menu.setMeta(new MetaVo(menu.getName(), menu.getIcon()));
                    }).collect(Collectors.toList());
            sysUtils.redisTemplate.opsForValue().set(SysConstants.MENU + loginId, menuDtos);
        }
        return menuDtos;
    }

    @Override
    public List<String> getMenuPermissionsByUserId(Object userId) {
        return baseMapper.selectBatchIds(getMenuIdsByUserId(userId)).stream().map(Menu::getPermission).collect(Collectors.toList());
    }

    @Override
    public List<MenuDto> getMenusByCondition(Menu menu) {
        List<MenuDto> data;

        if (ObjectUtil.isNull(menu.getType()) && StringUtils.isBlank(menu.getName())
                && ObjectUtil.isNull(menu.getVisible()) && ObjectUtil.isNull(menu.getStatus())) {
            List<MenuDto> menuDtoList = BeanUtil.copyToList(this.list(), MenuDto.class);
            data = menuDtoList.stream().filter(item -> item.getParentId() == 0)
                    .peek(item -> item.setChildren(getChildrenMenus(item.getId(), true, menuDtoList)))
                    .collect(Collectors.toList());
        } else {
            List<Menu> menuList = baseMapper.selectList(Wrappers.<Menu>lambdaQuery()
                    .eq(ObjectUtil.isNotNull(menu.getType()), Menu::getType, menu.getType())
                    .eq(ObjectUtil.isNotNull(menu.getStatus()), Menu::getStatus, menu.getStatus())
                    .eq(ObjectUtil.isNotNull(menu.getVisible()), Menu::getVisible, menu.getVisible())
                    .like(StringUtils.isNotBlank(menu.getName()), Menu::getName, menu.getName()));

            List<MenuDto> menuDtoList = BeanUtil.copyToList(menuList, MenuDto.class);

            for (int i = 0; i < menuList.size(); i++) {
                Integer id = menuList.get(i).getId();
                menuList.removeIf(item -> item.getParentId().equals(id));
            }

            List<MenuDto> menuDtos = BeanUtil.copyToList(menuList, MenuDto.class);

            data = menuDtos.stream().anyMatch(item -> item.getParentId() == 0) ?

                    menuDtos.stream().filter(item -> item.getParentId() == 0)
                            .peek(item -> item.setChildren(getChildrenMenus(item.getId(), true, menuDtoList)))
                            .collect(Collectors.toList()) :

                    menuDtos.stream().peek(item -> item.setChildren(getChildrenMenus(item.getId(), true, menuDtoList)))
                            .collect(Collectors.toList());
        }

        return data;
    }

    // 获取指定id用户的菜单id列表
    private List<Integer> getMenuIdsByUserId(Object userId) {
        List<Integer> roleIds = roleService.getRolesByUserId(userId).stream().map(Role::getId).collect(Collectors.toList());
        return roleMenuMapper.selectList(Wrappers.<RoleMenu>lambdaQuery().in(RoleMenu::getRoleId, roleIds))
                .stream().map(RoleMenu::getMenuId).collect(Collectors.toList());
    }

    // 递归查询子菜单
    private List<MenuDto> getChildrenMenus(Integer id, Boolean flag, List<MenuDto> menus) {
        return menus.stream()
                .filter(menu -> flag ?
                        menu.getParentId().equals(id) :
                        menu.getParentId().equals(id) && menu.getType() != MenuType.BUTTON && menu.getVisible() != ShowStatus.HIDE)
                .peek(menu -> {
                    menu.setChildren(getChildrenMenus(menu.getId(), flag, menus));
                    menu.setMeta(new MetaVo(menu.getName(), menu.getIcon()));
                }).collect(Collectors.toList());
    }

}
